package cn.sciento.boot.tenant.aspect;

import java.util.List;
import java.util.UUID;
import org.aopalliance.intercept.MethodInvocation;
import cn.sciento.boot.tenant.autoconfigure.TenantInitProperties;
import cn.sciento.boot.tenant.constant.TenantInitProcessorType;
import cn.sciento.boot.tenant.constant.TenantInitType;
import cn.sciento.boot.tenant.dto.TenantInitConfigDTO;
import cn.sciento.boot.tenant.dto.TenantInitContext;
import cn.sciento.boot.tenant.dto.TenantInitProcessContext;
import cn.sciento.boot.tenant.feign.DynamicFeignService;
import cn.sciento.boot.tenant.feign.TenantInitFeignService;
import cn.sciento.boot.tenant.processor.TenantInitGroupExecutor;
import cn.sciento.core.redis.RedisHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

/**
 * 租客初始化切面

 */
@Component
public class TenantInitAspect {
  private static final Logger LOGGER = LoggerFactory.getLogger(TenantInitAspect.class);
  private static final String TRUE_STRING = "true";
  @Autowired
  private RedisHelper redisHelper;
  @Autowired
  @Qualifier("tenantInitAsyncTaskExecutor")
  private AsyncTaskExecutor tenantInitAsyncTaskExecutor;
  @Autowired
  @Qualifier("groupOrderlyAsyncTaskExecutor")
  private AsyncTaskExecutor groupOrderlyAsyncTaskExecutor;
  @Autowired
  @Qualifier("groupWithoutOrderlyAsyncTaskExecutor")
  private AsyncTaskExecutor groupWithoutOrderlyAsyncTaskExecutor;
  @Autowired
  private DynamicFeignService dynamicFeignService;
  @Autowired
  private TenantInitProperties tenantInitProperties;

  public TenantInitAspect() {
  }

  /**
   * 调用租客初始化的前置处理器和后置处理器
   * @param invocation
   * @return
   * @throws Throwable
   */
  public Object createTenantInit(MethodInvocation invocation) throws Throwable {
    if (this.tenantInitProperties != null && this.tenantInitProperties.getAspect().isEnabled()) {
      LOGGER.info("create tenant point cut execution...");
      Object[] arguments = invocation.getArguments();
      TenantInitContext tenantInitContext = this.buildTenantInitContext(TenantInitType.CREATE, arguments);
      Object ret = null;

      try {
        this.asyncTenantInitProcess(tenantInitContext, TenantInitType.CREATE, TenantInitProcessorType.PRE_PROCESSOR);
      } catch (Throwable var7) {
        LOGGER.error("proceeding create tenant with error: ", var7);
      }

      ret = invocation.proceed();

      try {
        this.asyncTenantInitProcess(tenantInitContext, TenantInitType.CREATE, TenantInitProcessorType.POST_PROCESSOR);
        LOGGER.info("proceeding create tenant return {}.", ret);
      } catch (Throwable var6) {
        LOGGER.error("proceeding create tenant with error: ", var6);
      }

      return ret;
    } else {
      return invocation.proceed();
    }
  }

  public Object updateTenantInit(MethodInvocation invocation) throws Throwable {
    LOGGER.info("update tenant point cut execution...");
    Object[] arguments = invocation.getArguments();
    TenantInitContext tenantInitContext = this.buildTenantInitContext(TenantInitType.UPDATE, arguments);
    Object ret = null;

    try {
      this.asyncTenantInitProcess(tenantInitContext, TenantInitType.UPDATE, TenantInitProcessorType.PRE_PROCESSOR);
    } catch (Throwable var7) {
      LOGGER.error("proceeding update tenant with error: ", var7);
    }

    ret = invocation.proceed();

    try {
      this.asyncTenantInitProcess(tenantInitContext, TenantInitType.UPDATE, TenantInitProcessorType.POST_PROCESSOR);
      LOGGER.info("proceeding update tenant return {}.", ret);
    } catch (Throwable var6) {
      LOGGER.error("proceeding update tenant with error: ", var6);
    }

    return ret;
  }

  private TenantInitContext buildTenantInitContext(TenantInitType tenantInitType, Object[] parameters) {
    TenantInitContext tenantInitContext = new TenantInitContext();
    if (TenantInitType.CREATE.equals(tenantInitType)) {
      Assert.isTrue(parameters != null, "create tenant should have first parameter Tenant object.");
      BeanUtils.copyProperties(parameters[0], tenantInitContext);
    } else if (TenantInitType.UPDATE.equals(tenantInitType)) {
      Assert.isTrue(parameters != null, "update tenant should have second parameter Tenant object.");
      BeanUtils.copyProperties(parameters[1], tenantInitContext);
    }

    Assert.notNull(tenantInitContext, "tenant init context convert  failed.");
    tenantInitContext.setInstanceKey(UUID.randomUUID().toString());
    return tenantInitContext;
  }

  private void asyncTenantInitProcess(TenantInitContext tenantInitContext, TenantInitType tenantInitType, TenantInitProcessorType tenantInitProcessorType) {
    this.tenantInitAsyncTaskExecutor.execute(() -> {
      this.tenantInitProcess(tenantInitContext, tenantInitType, tenantInitProcessorType);
    });
  }

  /**
   * 远程调用相关的服务
   *
   * @param tenantInitContext 内容
   * @param tenantInitType 初始化的方式
   * @param tenantInitProcessorType 前置还是后置
   */
  private void tenantInitProcess(TenantInitContext tenantInitContext, TenantInitType tenantInitType, TenantInitProcessorType tenantInitProcessorType) {
    TenantInitConfigDTO tenantInitConfigDTO = new TenantInitConfigDTO();
    tenantInitConfigDTO.setProcessorType(tenantInitProcessorType);
    tenantInitConfigDTO.setInitType(tenantInitType);
    List<TenantInitConfigDTO> tenantInitConfigDTOs = tenantInitConfigDTO.doFetchCaches(this.redisHelper);
    if (!CollectionUtils.isEmpty(tenantInitConfigDTOs)) {
      TenantInitGroupExecutor.doGroupThenExecuteGroupOrderly(this.groupOrderlyAsyncTaskExecutor, tenantInitConfigDTOs, TenantInitConfigDTO::getOrderSeq, (orderSeq, itemList) -> {
        List<TenantInitConfigDTO> resultList = TenantInitGroupExecutor.doGroupThenExecuteGroupWithoutOrder(this.groupWithoutOrderlyAsyncTaskExecutor, itemList, TenantInitConfigDTO::getServiceName, (serviceName, serviceItemList) -> {
          try {
            TenantInitFeignService tenantInitFeignService = this.dynamicFeignService.newInstanceByName(TenantInitFeignService.class, String.format("http://%s", serviceName));
            TenantInitProcessContext tenantInitProcessContext = new TenantInitProcessContext(tenantInitContext, serviceItemList);
            tenantInitFeignService.process(tenantInitProcessContext);
          } catch (Exception e) {
            LOGGER.error("tenant init feign invoke fail", e);
          }

          return serviceItemList;
        });
        return resultList;
      });
    }
  }
}